import static java.lang.StrictMath.abs;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import model.ActionType;
import model.Game;
import model.Hockeyist;
import model.HockeyistState;
import model.HockeyistType;
import model.Move;
import model.Player;
import model.Puck;
import model.Unit;
import model.World;

abstract public class AbstractStrategy {

    public static Unit copyUnit(Unit unit, double x, double y, double speedX, double speedY) {
        return new Unit(unit.getId(), unit.getMass(), unit.getRadius(), x, y, speedX, speedY, unit.getAngle(), unit.getAngularSpeed()) {
        };
    }

    protected static <T> T[] concatAll(T[] first, T[]... rest) {
        int totalLength = first.length;
        for (T[] array : rest) {
            totalLength += array.length;
        }
        T[] result = Arrays.copyOf(first, totalLength);
        int offset = first.length;
        for (T[] array : rest) {
            System.arraycopy(array, 0, result, offset, array.length);
            offset += array.length;
        }
        return result;
    }

    protected static double WAIT_SWING_TICKS = 0;

    protected static double WAIT_PASS_TM_INDEX = -1;
    protected static double READY_TO_PASS_TICKS = 0;
    protected static double BACK_AFTER_SHOOT_TICKS = 20;
    protected final Coords coords;
    protected final Game game;
    protected final Move move;
    protected final Moving moving;
    protected final Puck puck;
    protected final double puckX;
    protected final double puckY;
    protected final Hockeyist self;
    protected final double selfX;
    protected final double selfY;
    protected final World world;

    protected final Point defendPoint;
    protected final Point defendPointFront;
    protected static double AGGRESSIVE_ATTACK_OPP_TICKS = 0;

    public AbstractStrategy(Hockeyist self, World world, Game game, Move move) {
        this.self = self;
        this.world = world;
        this.game = game;
        this.move = move;

        this.coords = new Coords(self, world, game, move);
        this.moving = new Moving(self, world, game, move);
        this.puck = world.getPuck();
        puckX = coords.getX(puck.getX());
        puckY = coords.getY(puck.getY());
        selfX = coords.getX(self.getX());
        selfY = coords.getY(self.getY());
        defendPoint = new Point(50, coords.getXcoord(1.01d), coords.getYcoord(coords.getY(getNet(true).getY()) * 0.5d));
        defendPointFront = new Point(50, coords.getXcoord(0.9d), coords.getYcoord(coords.getY(getNet(true).getY()) * 0.2d));

        if (READY_TO_PASS_TICKS > 0) {
            READY_TO_PASS_TICKS--;
        }
        if (BACK_AFTER_SHOOT_TICKS > 0) {
            BACK_AFTER_SHOOT_TICKS--;
        }
    }

    public Hockeyist getGoalie(boolean my) {
        for (Hockeyist h : world.getHockeyists()) {
            if (((my && h.getPlayerId() == world.getMyPlayer().getId()) || (!my && h.getPlayerId() != world.getMyPlayer().getId())) && h.getType() == HockeyistType.GOALIE) {
                return h;
            }
        }
        return null;
    }

    public Point getNet(boolean isMy) {
        Player player = isMy ? world.getMyPlayer() : world.getOpponentPlayer();

        double netX = 0.5D * (player.getNetBack() + player.getNetFront());
        double netY = 0.5D * (player.getNetBottom() + player.getNetTop());
        if (getGoalie(false) != null) {
            netY += (puck.getY() < netY ? 0.5D : -0.5D) * game.getGoalNetHeight();
        }
        return new Point(1, netX, netY);
    }

    public List<Unit> getOpponents(Unit compareUnit) {
        List<Unit> list = new ArrayList<>();
        for (Hockeyist h : world.getHockeyists()) {
            if (h.getPlayerId() == world.getOpponentPlayer().getId() && h.getType() != HockeyistType.GOALIE) {
                list.add((Unit) h);
            }
        }

        Collections.sort(list, new ComparatorTo(compareUnit));
        return list;
    }

    public List<Unit> getOppsAround(Unit hockeyist, double range) {
        List<Unit> result = new ArrayList<>();
        result.addAll(getOppsFront(hockeyist, range));
        result.addAll(getOppsBack(hockeyist, range));

        return result;
    }

    public List<Unit> getOppsBack(Unit hockeyist, double range) {
        List<Unit> list = new ArrayList<>();
        for (Unit h : getOpponents(self)) {
            if (hockeyist.getDistanceTo(h) < range && coords.getX(hockeyist.getX()) <= coords.getX(h.getX())) {
                list.add(h);
            }
        }
        return list;
    }

    public List<Unit> getOppsFront(Unit hockeyist, double range) {
        List<Unit> list = new ArrayList<>();
        for (Unit h : getOpponents(self)) {
            if (hockeyist.getDistanceTo(h) < range && coords.getX(hockeyist.getX()) > coords.getX(h.getX())) {
                list.add(h);
            }
        }
        return list;
    }

    public List<Unit> getTeammates(Unit compareUnit) {
        List<Unit> list = new ArrayList<>();
        for (Hockeyist h : world.getHockeyists()) {
            if (h.getPlayerId() == self.getPlayerId() && h.getType() != HockeyistType.GOALIE) {
                list.add(h);
            }
        }

        Collections.sort(list, new ComparatorTo(compareUnit));

        return list;
    }

    public Double getZone(Unit unit) {
        unit = moving.cast(unit, null, 0.0d, 10);
        double x = coords.getX(unit.getX());
        if (x < 0) {
            return 4 + abs((x * 3));
        } else if (x > 0) {
            return 4 - (x * 3);
        }

        return 3d;
    }

    public boolean hasPuck(Hockeyist h) {
        return puck.getOwnerHockeyistId() == h.getId();
    }

    public Unit nearTeammateWithoutCast(Unit unit) {
        double minDistance = Double.MAX_VALUE;
        Unit result = null;
        for (Unit tm : getTeammates(unit)) {
            if (tm.getDistanceTo(unit) < minDistance) {
                minDistance = tm.getDistanceTo(unit);
                result = tm;
            }
        }
        return result;
    }

    public Unit nearHockiyst(Unit unit) {
        List<Unit> hockeyists = getTeammates(unit);
        hockeyists.addAll(getOpponents(unit));

        Unit fUnit = moving.cast(unit, null, 0.0d, 0);
        double ticks = 0;
        while (ticks < 200) {
            Unit ffUnit = moving.cast(fUnit, null, 0.0d, 1);
            for (int i = 0; i < hockeyists.size(); i++) {
                Unit h = hockeyists.get(i);
                if (h.getDistanceTo(fUnit) < game.getStickLength() && moving.atSector(h, fUnit, game.getStickSector())) {
                    return h;
                } else {
                    hockeyists.set(i, moving.cast(h, ffUnit, 1.0d, 1));
                }
            }
            fUnit = ffUnit;
            ticks++;
        }

        return getTeammates(unit).get(0);
    }

    public Unit nearTeammate(Unit unit) {
        List<Unit> hockeyists = getTeammates(unit);

        Unit fUnit = moving.cast(unit, null, 0.0d, 0);
        double ticks = 0;
        while (ticks < 200) {
            Unit ffUnit = moving.cast(fUnit, null, 0.0d, 1);
            for (int i = 0; i < hockeyists.size(); i++) {
                Unit h = hockeyists.get(i);
                if (h.getDistanceTo(fUnit) < game.getStickLength() && moving.atSector(h, fUnit, game.getStickSector())) {
                    return h;
                } else {
                    hockeyists.set(i, moving.cast(h, ffUnit, 1.0d, 1));
                }
            }
            fUnit = ffUnit;
            ticks++;
        }

        return getTeammates(unit).get(0);
    }

    public boolean oppBlockUnit(Hockeyist hockeyist) {
        for (Unit hu : getOpponents(self)) {
            Hockeyist h = (Hockeyist) hu;
            if (puck.getOwnerHockeyistId() != h.getId()
                    && h.getState() != HockeyistState.KNOCKED_DOWN
                    && hockeyist.getDistanceTo(h) < game.getStickLength()
                    && Math.abs(hockeyist.getAngleTo(h)) < 0.5d * game.getStickSector()) {
                return true;
            }
        }
        return false;
    }

    public boolean oppFrontMeWithPuck(Hockeyist hockeyist) {
        for (Unit hu : getOpponents(self)) {
            Hockeyist h = (Hockeyist) hu;
            if (puck.getOwnerHockeyistId() == h.getId()
                    && hockeyist.getDistanceTo(h) < game.getStickLength()
                    && Math.abs(hockeyist.getAngleTo(h)) < 0.5d * game.getStickSector()
                    && coords.getX(hockeyist.getX()) < coords.getX(h.getX())) {
                return true;
            }
        }
        return false;
    }

    public abstract void run();

    protected void defendNet(boolean onFront) {
        Point point = !onFront ? defendPoint : defendPointFront;
        Unit myNet = getNet(true);
        moving.moveTo(point, puck);
    }

    private class ComparatorTo implements Comparator<Unit> {

        private final Unit cUnit;

        public ComparatorTo(Unit cUnit) {
            this.cUnit = cUnit;
        }

        @Override
        public int compare(Unit o1, Unit o2) {
            return ((Double) (cUnit.getDistanceTo(o1) - cUnit.getDistanceTo(o2))).intValue();
        }
    }

    protected Unit getUnitById(long id) {
        for (Hockeyist h : world.getHockeyists()) {
            if (h.getId() == id) {
                return h;
            }
        }
        return null;
    }

    protected Unit getUnitWithPuck() {
        for (Hockeyist h : world.getHockeyists()) {
            if (puck.getOwnerHockeyistId() == h.getId()) {
                return h;
            }
        }
        return null;
    }

    protected void pass(Unit unit) {
        move.setTurn(self.getAngleTo(unit));
        for (Hockeyist h : world.getHockeyists()) {
            if (h.getId() == unit.getId()) {
                WAIT_PASS_TM_INDEX = h.getTeammateIndex();
                break;
            }
        }
        if (moving.atSector(self, unit, game.getPassSector())) {
            double passPower = 1.0d;
            while (passPower > 0.1d) {

                double puckSpeed = 15 * game.getPassPowerFactor() * passPower + moving.getSpeed(self)
                        * Math.cos(self.getAngle() + self.getAngleTo(unit) - Math.atan2(self.getSpeedY(), self.getSpeedX()));
                double ticks = 0;
                boolean stop = false;
                Unit passingPuck = copyUnit(puck, puck.getX(), puck.getY(), puckSpeed * Math.cos(self.getAngle() + self.getAngleTo(unit)), puckSpeed * Math.sin(self.getAngle() + self.getAngleTo(unit)));
                while (ticks < 100 && !stop) {
                    if (passingPuck.getDistanceTo(unit) < unit.getRadius()) {
                        if (moving.getSpeed(passingPuck) < 10.0d) {
                            move.setPassPower(passPower);
                            move.setPassAngle(self.getAngleTo(unit));
                            move.setAction(ActionType.PASS);
                            READY_TO_PASS_TICKS = 30;
                            return;
                        } else {
                            stop = true;
                            break;
                        }
                    } else {
                        passingPuck = moving.cast(passingPuck, null, 0.0d, 1);
                    }
                    ticks++;
                }
                passPower -= 0.1d;
            }
            move.setPassPower(1.0d);
            move.setPassAngle(self.getAngleTo(unit));
            move.setAction(ActionType.PASS);
            READY_TO_PASS_TICKS = 30;
        }
    }
}
